﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;

namespace Maze
{

 
    public  class MazeCreator
    {

        #region Events and related
        //za promenu lavirinta
        public event EventHandler<PointEventArgs> MazeChanged;


        protected void OnTileDeleted(Point p)
        {
            if (MazeChanged != null)
            {
                MazeChanged(this, new PointEventArgs(p));
            }
        }

        #endregion


        #region Variables and properties

        /// <summary>
        /// The maze as it looks now. Empty Points are zeroes and walls are ones
        /// </summary>
        public byte[,] Maze { get; private set; }

        //The stack of points not tested yet
        private Stack<Point> _tiletoTry = new Stack<Point>();

        /// <summary>
        /// The list of offsets to use to get the tiles above, below, to the right and the left of a specific tile
        /// </summary>
        private List<Point> NSEW = new List<Point> { new Point(0, 1), new Point(0, -1), new Point(1, 0), new Point(-1, 0) };

        /// <summary>
        /// Used to generate random values
        /// </summary>
        private Random rnd = new Random();


        private int _width, _height;
        private Point _currentTile;

        /// <summary>
        /// The width of the maze
        /// </summary>
        public int Width
        {
            get { return _width; }
            private set
            {
                if (value < 3)
                {
                    throw new ArgumentException("Width must be larger than two for the generator to work");
                }
                 if (value % 2 == 0)
                {
                    throw new ArgumentException("Width must be an odd number for the generator to work");
                }

                _width = value;
            }
        }

         public int Height
        {

            get { return _height; }
            private set
            {
                if (value < 3)
                {
                    throw new ArgumentException("Height must be larger than two for the generator to work");
                }
                if (value % 2 == 0)
                {
                    throw new ArgumentException("Height must be an odd number for the generator to work");
                }

                _height = value;

            }

        }

         public Point CurrentTile
        {
            get { return _currentTile; }
            private set
            {
                if (value.X < 1 || value.X >= this.Width - 1 || value.Y < 1 || value.Y >= this.Height - 1)
                {
                    throw new ArgumentException("CurrentTile must be within the one tile border all around the maze");
                }
                if (value.X % 2 == 1 || value.Y % 2 == 1)
                { _currentTile = value; }
                else
                {
                    throw new ArgumentException("The current square must not be both on an even X-axis and an even Y-axis, to ensure we can get walls around all tunnels");
                }
            }

        }

        #endregion


        #region Constructor
    public MazeCreator(int width, int height, Point startingPosition)
        {
            if (width * height % 2 == 0)
            {
                throw new ArgumentException(string.Format("Only odd values are allowed for width and height - you provided values ({0},{1})", width, height));
            }

            this.Width = width;
            this.Height = height;

            Maze = new byte[Width, Height];

            for (int x = 0; x < Width; x++)
            {
                for (int y = 0; y < Height; y++)
                {
                    Maze[x, y] = 1;
                }
            }

            CurrentTile = startingPosition;
            _tiletoTry.Push(CurrentTile);
        }

        #endregion


        #region CreateMaze

        public byte[,] CreateMaze()
        {
            List<Point> neighbors;

            while (_tiletoTry.Count > 0)
            {
                Maze[CurrentTile.X, CurrentTile.Y] = 0;
                OnTileDeleted(CurrentTile);

                neighbors = GetValidNeighbors(CurrentTile);
       if (neighbors.Count > 0)
                {
                     _tiletoTry.Push(CurrentTile);
                     CurrentTile = neighbors[rnd.Next(neighbors.Count)];
                }
                else
                {
                     CurrentTile = _tiletoTry.Pop();
                }
            }

            Console.WriteLine(Maze.ToString());

            return Maze;
        }

        #endregion


        #region Helpermethods

        private List<Point> GetValidNeighbors(Point centerTile)
        {

            List<Point> validNeighbors = new List<Point>();
    foreach (var offset in NSEW)
            {
                Point toCheck = new Point(centerTile.X + offset.X, centerTile.Y + offset.Y);

                  if (toCheck.X % 2 == 1 || toCheck.Y % 2 == 1)
                {
                     if (Maze[toCheck.X, toCheck.Y] == 1 && HasThreeWallsIntact(toCheck))
                    {
                        validNeighbors.Add(toCheck);
                    }
                }
            }

            return validNeighbors;
        }


           private bool HasThreeWallsIntact(Point pointToCheck)
        {
            int intactWallCounter = 0;

            foreach (var offset in NSEW)
            {
                 Point neighborToCheck = new Point(pointToCheck.X + offset.X, pointToCheck.Y + offset.Y);

                if (IsInside(neighborToCheck) && Maze[neighborToCheck.X, neighborToCheck.Y] == 1)
                {
                    intactWallCounter++;
                }
            }

             return intactWallCounter == 3;

        }

         private bool IsInside(Point p)
        {
            return p.X >= 0 && p.Y >= 0 && p.X < Width && p.Y < Height;
        }

        #endregion


        #region ToString

         public override string ToString()
        {
            string representation = " ";
            StringBuilder sb = new StringBuilder();
            for (int y = Height - 1; y >= 0; y--)
            {
                for (int x = 0; x < Width; x++)
                {
                    if (Maze[x, y] == 0)
                    {
                       if (CurrentTile.X == x && CurrentTile.Y == y)
                        {
                            representation = "O";
                        }
                        else
                             if (_tiletoTry.Contains(new Point(x, y)))
                            {
                                representation = ".";
                            }
                            else
                            {
                                 representation = " ";
                            }
                    }
                    else
                    {
                        representation = "X";
                    }
                    sb.Append(representation);
                }
                sb.AppendLine();
            }
            return sb.ToString();
        }
        public int[] returnBegin()
        {

            string representation = " ";
            int[] ret = { };

            StringBuilder sb = new StringBuilder();
            for (int y = Height - 1; y >= 0; y--)
            {
                for (int x = 0; x < Width; x++)
                {
                    if (Maze[x, y] == 0)
                    {
                        if (CurrentTile.X == x && CurrentTile.Y == y)
                        {
                            representation = "L";
                            ret = new int[] { x, y };

                        }
                        else
                             if (_tiletoTry.Contains(new Point(x, y)))
                            {
                                representation = ".";
                            }
                            else
                            {
                                representation = " ";
                            }
                    }
                    else
                    {
                        representation = "X";
                    }
                    sb.Append(representation);
                }
                sb.AppendLine();
            }
            return ret;
        }



        #endregion

        #region ToBitmap

        public Bitmap ConvertToBitmap(int tileSizeInPixels)
        {
             Bitmap bmp = new Bitmap(1 + (tileSizeInPixels + 1) * this.Width, 1 + (tileSizeInPixels + 1) * this.Height);
            Graphics g = Graphics.FromImage(bmp);

             g.FillRectangle(Brushes.DarkGray, new Rectangle(0, 0, bmp.Width, bmp.Height));

         
            string mazeString = this.ToString().Replace(Environment.NewLine, "");

         
            Brush fillBrush = Brushes.White;

        
            for (int y = 0; y < this.Height; y++)
            {
                for (int x = 0; x < this.Width; x++)
                {

                    switch (mazeString[x + y * this.Width])
                    {

                        case 'X': fillBrush = Brushes.Black;
                            break;
                        case 'O':
                            fillBrush = Brushes.Red;
                            break;
                        case '.':
                            fillBrush = Brushes.CornflowerBlue;
                            break;
                        case ' ':
                            fillBrush = Brushes.White;
                            break;
                    }
                
                    g.FillRectangle(fillBrush, 1 + x * (tileSizeInPixels + 1), 1 + y * (tileSizeInPixels + 1), tileSizeInPixels, tileSizeInPixels);

                }
            }
          
            g.Dispose();
            return bmp;
        }

        #endregion

    }
}
